/*
	Copyright (C) 2009 Salil Bhagurkar
	
	This file is part of ViSE
	
	ViSE is free software: you can redistribute it and/or modify
	it under the terms of the GNU Lesser General Public License as published by
	the Free Software Foundation, either version 3 of the License, or
	(at your option) any later version.
	
	ViSE is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU Lesser General Public License for more details.
	
	You should have received a copy of the GNU Lesser General Public License
	along with ViSE.  If not, see <http://www.gnu.org/licenses/>.
*/


//Reads partition table from hd0

#include <shared.h>


#define POFF1 0x1be
#define POFF2 0x1ce
#define POFF3 0x1de
#define POFF4 0x1ee

/*Volume driver for partition tables*/

/*The different volume system type codes from GRUB docs..:*/
/*		0		1		2		3		4		5		6		7		8		9		a		b		c		d		e		f */
char *pt[]={ \
/*0*/ "Empty","FAT12","XENIX","XENIX","FAT16","DOSEx","FAT16","NTFS ","AIX	","AIX  ","OS/2B","FAT32","FAT32","		","FAT16","W95ex", \
/*1*/ "OPUS ","fat12","Compq","		","FAT16","		","FAT16","NTFS ","AST  ","		","		","		","		","		","		","		", \
/*2*/ "		","		","		","		","NEC  ","		","		","		","		","		","		","		","		","		","		","		", \
/*3*/ "		","		","		","		","		","		","		","		","		","		","		","		","pmagr","		","		","		", \
/*4*/ "VENIX","LINUX","SFS  ","Linux","		","		","		","		","		","		","		","		","		","		","		","		", \
/*5*/ "DM   ","DM6  ","CP/M ","DM6  ","DM6  ","EZ-dr","Goldb","		","		","		","		","		","Priam","		","		","		", \
/*6*/ "		","Speed","		","GNUhu","Novel","Novel","		","		","		","		","		","		","		","		","		","		", \
/*7*/ "DiskS","		","		","		","		","PC/IX","		","QNX4.","QNX4.","QNX4.","		","		","		","		","		","		", \
/*8*/ "MINIX","MINIX","LINUX","LINUX","OS/2 ","LINUX","NTFSv","NTFSv","		","		","		","		","		","		","		","		", \
/*9*/ "		","		","		","Amoeb","Amoeb","		","		","		","		","		","		","		","		","		","		","		", \
/*a*/ "IBMth","		","		","		","		","BSD  ","		","NextS","		","		","		","		","		","		","		","		", \
/*b*/ "		","		","		","		","		","		","		","BSDI ","BSDI ","		","		","		","		","		","		","		", \
/*c*/ "		","DRDOS","		","		","DRDOS","		","DRDOS","Syrin","		","		","		","		","		","		","		","		", \
/*d*/ "		","		","		","		","		","		","		","		","		","		","		","CP/M ","		","		","		","		", \
/*e*/ "		","DOS  ","		","DOS  ","Speed","		","		","		","		","		","		","		","		","		","		","		", \
/*f*/ "		","Speed","DOS3.","		","Speed","		","		","		","		","		","		","		","		","		","Speed","Xenix"};

/*A partition table entry*/
struct pentry	{
	u8 boot;
	u8 bhead;
	u8 bsecthcyl;
	u8 blcyl;
	u8 sys;
	u8 ehead;
	u8 esecthcyl;
	u8 elcyl;
	u32 preceding_sectors;
	u32 nsect;
}__attribute__((packed));



//partition table management code


#define SECT_SIZE 512


static u32 extoff=0;
static u32 vol_counter = 0;


struct volinfo {
	u8 system;
	u32 start;
	u32 end;
};

static int inline check_vol_operation(struct volinfo *v, u32 offset, u32 count)
{
	return (offset <= (v->end - v->start) && count <= (v->end - offset));
}


int vol_read(struct node_fs *this, void *buf, u32 offset, u32 count)
{
	struct volinfo *v;
	v = GET_CLASS(this, struct volinfo);
	if(check_vol_operation(v, offset, count)) {
		this->parent->read(this->parent, buf, v->start + offset, count);
	} else
		return EPBOUND;
	return 0;
}

int vol_write(struct node_fs *this, void *buf, u32 offset, u32 count)
{
	struct volinfo *v;
	v = GET_CLASS(this, struct volinfo);
	if(check_vol_operation(v, offset, count)) {
		this->parent->read(this->parent, buf, v->start + offset, count);
	} else
		return EPBOUND;
	return 0;
}


void plug_vol(struct node_fs *dev, u8 system, u32 start, u32 end)
{
	struct node_fs *n;
	struct volinfo *v;
	char volname[FS_NAME_LEN];
	printk("Found volume (%x) %u to %u\n", system, start, end);
	snprintf(volname, FS_NAME_LEN, "vol%u", vol_counter++);
	n = fs_create(volname);
	v = (struct volinfo *)malloc(sizeof(struct volinfo));
	v->system = system;
	v->start = start;
	v->end = end;
	n->class = v;
	n->read = vol_read;
	n->write = vol_write;
	n->type = TYPE_FS_BOTH;
	n->op_unit = 512;
	fs_noparse_link(dev, n);
}


int read_ptextended(struct node_fs *dev, u32 lba)
{
	int i;
	int err;
	struct pentry *x;
	void *buffer;
	buffer = malloc(SECT_SIZE);
	if(lba > dev->count) {
		printk("ptable extended:lba out of disk limit\n");
		free(buffer);
		return ELLERR;
	}
	err = dev->read(dev, buffer, lba, 1);
	if(err) {
		printk("ptable extended: read failed\n");
		free(buffer);
		return ELLERR;
	}
	for(i=0;i<4;i++)	{
		x=(struct pentry *)(buffer+POFF1+i*0x10);
		if(x->sys==0x5 || x->sys==0xf)	{
			read_ptextended(dev, extoff+x->preceding_sectors);
		} else	if(x->sys)	{
			if((lba+x->preceding_sectors) > (dev->count))
				printk("\nptable entry out of limits");
			else
				plug_vol(dev, x->sys,lba+x->preceding_sectors,x->nsect);
		}
	}
	free(buffer);
	return 0;
}

static int enumerate_volumes(struct node_fs *dev)
{
	int i;
	int err=0;
	struct pentry *x;
	u8 *buffer;
	buffer = (u8 *)malloc(SECT_SIZE);
	err = dev->read(dev, buffer, 0, 1);
	if(err) {
		free(buffer);
		return err;
	}
	if((u16)(*((u16 *)(buffer+0x1fe)))!=0xaa55)	{
		printk("Partition table sector invalid\n");
		free(buffer);
		return ENOSUP;
	}
	for(i=0;i<4;i++)	{
		x=(struct pentry *)(buffer+POFF1+i*0x10);
		if(x->sys==0x5 || x->sys==0xf)	{
			extoff=x->preceding_sectors;
			read_ptextended(dev, x->preceding_sectors);
		} else	if(x->sys)	{
			if((x->preceding_sectors) > (dev->count))
				printk("ptable entry out of limits\n");
			else {
				plug_vol(dev, x->sys,x->preceding_sectors,x->nsect);
			}
		}
	}
	free(buffer);
	return 0;
}

static int part_enum(struct node_fs *this)
{
	//We are enumerating an hd device which will be our parent
	enumerate_volumes(this->parent);
	return 0;
}


static void make_enum(struct node_fs *dev)
{
	if(!dev) {
		dev = fs_get("/class/storage/hd0");
		if(!dev)
			return;
	}
	if(dev->name[0] == 'h' && dev->name[1] == 'd') //No floppies
		fs_noparse_new_enumerator(dev, part_enum, "part_enumerator");
}	


static int part_notify(struct node_fs *this, struct node_fs *link)
{
	//We have been notified that a new storage device has been registered to /class/storage.
	//Now we must enumerate its volumes if someone tries to explore the contents of the storage device
	make_enum(link);
}


int part_driver_read(struct node_fs *this, void *buf, u32 offset, u32 count)
{
	return 0;
}

int part_driver_write(struct node_fs *this, void *buf, u32 offset, u32 count)
{
	switch(offset) {
		case DRIVER_START:
			fs_new_notifier("/class/storage", part_notify, "part_notifier");
			make_enum(NULL);
			return 0;
		default:
			return ENOSUP;
	}
	return 0;
}


struct node_fs part_driver = {
	.type = TYPE_FS_BOTH,
	.name = "part_driver",
	NODE_FS_INIT_VARS,
	.read = part_driver_read,
	.write = part_driver_write,
	.enumerate = dummy,
	.notify = dummy,
	NODE_FS_INIT_INIT,
	NODE_FS_LIST_INIT,
};


int register_part()
{
	
	fs_link("/driver", &part_driver);
	return 0;
}
